/*
 * @Author: wuqingchun
 * @Date: 2023-12-28 23:04:07
 * @Description: 
 * @LastEditors: wuqingchun
 * @LastEditTime: 2024-01-11 15:46:46
 */

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#ifdef USE_RL
#include <readline/readline.h>
#include <readline/history.h>
#endif
#include "ftpc.h"
#include "ftpc_util.h"
#include "ftpc_ctl.h"
#include "ftpc_dtp.h"
#include "ftpc_event.h"
#include "ftpc_log.h"
#include "ftpc_stdin.h"


// 协商数据连接端口号
static int __stdin_NegotiatePort(char* pcTransferCmd, FTPC_TRANSFER_TYPE_E enTransferType)
{
    int iRet = -1;

    if (BIT_TEST(g_stFTPCClient.uiFlags, FTPC_FLAG_PASSIVE))
    {
        // FTP 服务端被动等待客户端建立数据连接，被动模式(Passive)
        // FTP 服务端处理客户端 PASV 请求，选择一个数据端口进行监听，通过应答告知客户端IP地址和端口，等待客户端建立数据连接请求
        iRet = FTPC_ctl_SendRequest(FTPC_CMD_PASV, NULL);
    }
    else
    {
        struct sockaddr_in stAddr;
        // FTP服务端主动发起建立数据连接请求，主动模式(Active)
        // FTP 客户端选择一个数据端口进行监听，通过PORT命令告知服务端IP地址和端口，等待服务端建立数据连接请求
        iRet = FTPC_dtp_Listen(g_stFTPCClient.iCtlSock, &stAddr);
        if (iRet == 0)
        {
            stAddr.sin_port = ntohs(stAddr.sin_port); // 主机序
            char *pcData = (char *)&stAddr.sin_addr.s_addr;
            char *pcPort = (char *)&stAddr.sin_port;

            iRet = FTPC_ctl_SendRequest(FTPC_CMD_PORT, "%hhu,%hhu,%hhu,%hhu,%hhu,%hhu", 
                                        pcData[0], pcData[1], pcData[2], pcData[3], pcPort[1], pcPort[0]);
            if (iRet != 0)
            {
                FTPC_dtp_CloseSock();
            }
        }
    }

    if (iRet == 0)
    {
        if (g_stFTPCClient.enState == FTPC_STATE_LOGIN_SUCCESS)
        {
            g_stFTPCClient.pcTransferCmd = pcTransferCmd;
            FTPC_dtp_SetTransferType(enTransferType);

            g_stFTPCClient.enState = FTPC_STATE_NEGOTIATE_PORT;
        }
    }

    return iRet;
}


static int __stdin_Close(FTPC_CMD_INFO_S* pstCmdInfo)
{
    int iErr = -1;

    if (g_stFTPCClient.iCtlSock == -1)
    {
        printf("Not connected.\n");
        return iErr;
    }

    // 控制连接已经建立，发送QUIT请求，等应答后退出
    if (FTPC_ctl_SendRequest(FTPC_CMD_QUIT, NULL) == 0)
    {
        g_stFTPCClient.enState = FTPC_STATE_DISCONNECTED;
    }
}

static int __stdin_SetDebug(FTPC_CMD_INFO_S* pstCmdInfo)
{
    if (BIT_TEST(g_stFTPCClient.uiFlags, FTPC_FLAG_DEBUG))
    {
        printf("Debugging off (debug=0).\n");
        BIT_CLR(g_stFTPCClient.uiFlags, FTPC_FLAG_DEBUG);
    }
    else
    {
        BIT_SET(g_stFTPCClient.uiFlags, FTPC_FLAG_DEBUG);
        printf("Debugging on (debug=1).\n");
    }

    return 0;
}

static int __stdin_Open(FTPC_CMD_INFO_S* pstCmdInfo)
{
    unsigned short usPort;
    char* pcHostName;
    int iErr = -1;

    if (g_stFTPCClient.iCtlSock != -1)
    {
        printf("Already connected to %s, use close first.\n", g_stFTPCClient.szHostName);
        return -1;
    }

    iErr = 0;
    usPort = FTPC_SERVER_PORT_DEFAULT;
    pcHostName = pstCmdInfo->apcItems[1];

    if (pstCmdInfo->uiCount == 1)
    {
        int iPort;
        iErr = FTPC_util_GetUserInput("(to) ", pstCmdInfo);
        if (iErr == 0)
        {
            pcHostName = pstCmdInfo->apcItems[0];
            if (pstCmdInfo->uiCount > 1)
            {
                iPort = atoi(pstCmdInfo->apcItems[1]);
                if (0 < iPort && iPort < 65536)
                {
                    usPort = iPort;
                }
                else
                {
                    iErr = -1;
                }
            }
        }
    }
    else if (pstCmdInfo->uiCount == 3)
    {
        int iPort = atoi(pstCmdInfo->apcItems[2]);
        if (0 < iPort && iPort < 65536)
        {
            usPort = iPort;
        }
    }
    else if (pstCmdInfo->uiCount > 3)
    {
        iErr = -1;
    }

    pstCmdInfo->uiCount = 0;

    if (iErr != 0)
    {
        printf("usage: open host-name [port]\n");
        return -1;
    }

    return FTPC_ctl_Connect(pcHostName, usPort);
}

static int __stdin_Quit(FTPC_CMD_INFO_S* pstCmdInfo)
{
    if (g_stFTPCClient.iCtlSock == -1)
    {
        // 没有建立连接，直接退出
        exit(0);
    }

    // 控制连接已经建立，发送QUIT请求，等应答后退出
    if (FTPC_ctl_SendRequest(FTPC_CMD_QUIT, NULL) == 0)
    {
        g_stFTPCClient.enState = FTPC_STATE_QUIT;
    }
    else
    {
        exit(0);
    }
}

static int __stdin_Login(FTPC_CMD_INFO_S* pstCmdInfo)
{
    char* pcUserName;
    int iErr = -1;

    if (g_stFTPCClient.iCtlSock == -1)
    {
        printf("Not connected.\n");
        return iErr;
    }

    pcUserName = pstCmdInfo->apcItems[1];

    iErr = 0;
    if (pstCmdInfo->uiCount == 1)
    {
        iErr = FTPC_util_GetUserInput("(username) ", pstCmdInfo);
        if (iErr == 0)
        {
            pcUserName = pstCmdInfo->apcItems[0];
            pstCmdInfo->uiCount = 0;
        }
    }
    else if (pstCmdInfo->uiCount > 4)
    {
        iErr = -1;
    }

    if (iErr != 0)
    {
        printf("usage: user username [password] [account]\n");
    }
    else
    {
        iErr = FTPC_ctl_SendRequest(FTPC_CMD_USER, pcUserName);
        g_stFTPCClient.enRecoverState = g_stFTPCClient.enState;
        g_stFTPCClient.enState = FTPC_STATE_LOGIN_USER;
    }

    return iErr;
}

static int __stdin_ListDir(FTPC_CMD_INFO_S* pstCmdInfo)
{
    int iRet = -1;
    char szData[FTPC_CLIENT_BUF_MAXLEN + 1];
    ssize_t lSendRet;

    if (g_stFTPCClient.iCtlSock == -1)
    {
        printf("Not connected.\n");
        return iRet;
    }

    if (pstCmdInfo->uiCount > 3)
    {
        printf("usage: dir remote-directory local-file\n");
        return iRet;
    }

    if (pstCmdInfo->uiCount == 3)
    {
        printf("output to local-file: %s? ", pstCmdInfo->apcItems[2]);

        FTPC_CMD_INFO_S stCmdInfo;
        char szTemp[8];
        iRet = FTPC_util_ReadLine(&stCmdInfo, (char**)&szTemp);
        if (iRet == 0)
        {
            if (*stCmdInfo.apcItems[0] == 'n' || *stCmdInfo.apcItems[0] == 'N')
            {
                return -1;
            }
        }
    }

    return __stdin_NegotiatePort(FTPC_CMD_LIST, FTPC_TRANSFER_LIST);
}

static int __stdin_GetFile(FTPC_CMD_INFO_S* pstCmdInfo)
{
    int iRet = -1;
    char* pcOutput;

    if (g_stFTPCClient.iCtlSock == -1)
    {
        printf("Not connected.\n");
        return iRet;
    }

    if (pstCmdInfo->uiCount < 2)
    {
        iRet = FTPC_util_GetUserInput("(remote-file) ", pstCmdInfo);
        if (iRet != 0)
        {
            printf("usage: get remote-file [ local-file ]\n");
            return iRet;
        }

        pstCmdInfo->apcItems[2] = pstCmdInfo->apcItems[1];
        pstCmdInfo->apcItems[1] = pstCmdInfo->apcItems[0];
        pstCmdInfo->apcItems[0] = "get";
        pstCmdInfo->uiCount++;
    }

    pcOutput = pstCmdInfo->apcItems[1];
    if (pstCmdInfo->uiCount > 2)
    {
        pcOutput = pstCmdInfo->apcItems[2];
    }

    printf("local: %s remote: %s\n", pstCmdInfo->apcItems[1], pcOutput);

    return __stdin_NegotiatePort(FTPC_CMD_RETR, FTPC_TRANSFER_GET);
}

static int __stdin_PutFile(FTPC_CMD_INFO_S* pstCmdInfo)
{
    int iRet = -1;
    char* pcRemote;

    if (g_stFTPCClient.iCtlSock == -1)
    {
        printf("Not connected.\n");
        return iRet;
    }

    if (pstCmdInfo->uiCount < 2)
    {
        iRet = FTPC_util_GetUserInput("(local-file) ", pstCmdInfo);
        if (iRet != 0)
        {
            printf("usage: put local-file remote-file\n");
            return iRet;
        }

        pstCmdInfo->apcItems[2] = pstCmdInfo->apcItems[1];
        pstCmdInfo->apcItems[1] = pstCmdInfo->apcItems[0];
        pstCmdInfo->apcItems[0] = "put";
        pstCmdInfo->uiCount++;
    }

    pcRemote = pstCmdInfo->apcItems[1];
    if (pstCmdInfo->uiCount > 2)
    {
        pcRemote = pstCmdInfo->apcItems[2];
    }
    
    printf("local: %s remote: %s\n", pstCmdInfo->apcItems[1], pcRemote);

    // 检查本地文件是否存在
    struct stat stStat;
    iRet = stat(pstCmdInfo->apcItems[1], &stStat);
    if (iRet != 0)
    {
        printf("local: %s: %s\n", pstCmdInfo->apcItems[1], strerror(errno));
        return iRet;
    }

    return __stdin_NegotiatePort(FTPC_CMD_STOR, FTPC_TRANSFER_PUT);
}

static int __stdin_SetPassive(FTPC_CMD_INFO_S* pstCmdInfo)
{
    if (BIT_TEST(g_stFTPCClient.uiFlags, FTPC_FLAG_PASSIVE))
    {
        BIT_CLR(g_stFTPCClient.uiFlags, FTPC_FLAG_PASSIVE);
        printf("Passive mode off.\n");
    }
    else
    {
        BIT_SET(g_stFTPCClient.uiFlags, FTPC_FLAG_PASSIVE);
        printf("Passive mode on.\n");
    }

    return 0;
}

static int __stdin_SetMode(FTPC_CMD_INFO_S* pstCmdInfo)
{
    printf("We only support stream mode, sorry.\n");
}

static int __stdin_TMode(FTPC_CMD_INFO_S* pstCmdInfo)
{
    int iErr = -1;

    if (pstCmdInfo->uiCount != 2)
    {
        printf("usage: tmode <normal|mmap|sendfile|directio>\n");
        return iErr;
    }

    iErr = 0;
    if (strcmp(pstCmdInfo->apcItems[1], "normal") == 0)
    {
        g_stFTPCClient.stDTPCtx.enSendMode = FTPC_SEND_MODE_NORMAL;
    }
    else if (strcmp(pstCmdInfo->apcItems[1], "mmap") == 0)
    {
        g_stFTPCClient.stDTPCtx.enSendMode = FTPC_SEND_MODE_MMAP;
    }
    else if (strcmp(pstCmdInfo->apcItems[1], "sendfile") == 0)
    {
        g_stFTPCClient.stDTPCtx.enSendMode = FTPC_SEND_MODE_SENDFILE;
    }
    else if (strcmp(pstCmdInfo->apcItems[1], "directio") == 0)
    {
        g_stFTPCClient.stDTPCtx.enSendMode = FTPC_SEND_MODE_DIRECT_IO;
    }
    else
    {
        printf("usage: tmode <normal|mmap|sendfile|directio>\n");
        iErr = -1;
    }

    return iErr;
}

static int __stdin_ShowHelp(FTPC_CMD_INFO_S* pstCmdInfo);

static FTPC_CMD_S g_astFTPCCommands[] = 
{
    {"close", __stdin_Close, 0},
    {"debug", __stdin_SetDebug, 0},
    {"dir", __stdin_ListDir, 2},
    {"get", __stdin_GetFile, 2},
    {"help", __stdin_ShowHelp, -1},
    {"open", __stdin_Open, 2},
    {"mode", __stdin_SetMode, 0},
    {"put", __stdin_PutFile, 2},
    {"passive", __stdin_SetPassive, 0},
    {"quit", __stdin_Quit, 0},
    {"tmode", __stdin_TMode, 1},
    {"user", __stdin_Login, 2},
};

static int __stdin_ShowHelp(FTPC_CMD_INFO_S* pstCmdInfo)
{
    printf("Commands may be abbreviated.  Commands are:\n\n");
    
    for (int i = 0; i < ARRAY_SIZE(g_astFTPCCommands); i++)
    {
        printf("%-16s", g_astFTPCCommands[i].pcName);

        if ((i+1) % 5 == 0)
        {
            printf("\n");
        }
    }

    printf("\n");

    return 0;
}

static FTPC_CMD_S* __stdin_FindCmd(const char *pcCmdName)
{
    unsigned int uiIndex = 0;
    FTPC_CMD_S* pstCmd;

    for (unsigned int uiIndex = 0; uiIndex < ARRAY_SIZE(g_astFTPCCommands); uiIndex++)
    {
        pstCmd = &g_astFTPCCommands[uiIndex];
        if (strcmp(pcCmdName, pstCmd->pcName) == 0)
        {
            return pstCmd;
        }
    }

    return NULL;
}

static int __stdin_HandleCmd(FTPC_CMD_INFO_S* pstCmdInfo)
{
    int iErr = -1;

    FTPC_CMD_S* pstCmd = __stdin_FindCmd(pstCmdInfo->apcItems[0]);
    if (pstCmd == NULL)
    {
        printf("?Invalid command\n");
    }
    else
    {
#ifndef USE_RL
        FTPC_event_DeleteFd(STDIN_FILENO);
#endif
        iErr = pstCmd->pfHandler(pstCmdInfo);
    }

    return iErr;
}

// 处理标准输入
int FTPC_stdin_Read(void)
{
    FTPC_CMD_INFO_S* pstCmdInfo = &g_stFTPCClient.stCmdInfo;
    char *pcLine = g_stFTPCClient.szCmdLine;
    int iErr;

    FTPC_LOG_DEBUG("Read data from stdin");

    iErr = FTPC_util_ReadLine(pstCmdInfo, &pcLine);
    if (iErr == 0)
    {
        iErr = __stdin_HandleCmd(pstCmdInfo);
    }

    return iErr;
}

int FTPC_stdin_WaitInput(void)
{
    int iRet = 0;
    
#ifdef USE_RL
    int iErr;
    char* pcInput = readline("ftp> ");
    if (pcInput != NULL)
    {
        strcpy(g_stFTPCClient.szCmdLine, pcInput);
        iErr = FTPC_util_ParseLine(&g_stFTPCClient.stCmdInfo, g_stFTPCClient.szCmdLine);
        if (iErr == 0)
        {
            add_history(pcInput);
            iErr = __stdin_HandleCmd(&g_stFTPCClient.stCmdInfo);
        }

        free(pcInput);
    }

    iRet = 1;
#else
    FTPC_event_AddFd(STDIN_FILENO, FTPC_EVENT_IN);
    printf("ftp> ");
    fflush(stdout);
#endif

    return iRet;
}

void FTPC_stdin_Init(void)
{
}